/// Copyright (c) Microsoft Corporation.  All rights reserved.

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Net;
using System.Threading;

namespace Microsoft.VSPowerToys.Common.TaskManager
{

    /// <summary>
    /// A task to download files from HTTP servers.
    /// </summary>
    public class DownloadTask : Task
    {

        #region Private Members

        private const int BLOCK = 20000;
        
        private Uri urlToDownload;
        
        private string saveLocation;

        /// <summary>
        /// If set to true, task will be cancelled on the next block
        /// </summary>
        private bool cancelFlag = false;
        #endregion

        #region Public Properties

        /// <summary>
        /// URL of the file to download
        /// </summary>
        public Uri Url
        {
            get
            {
                return urlToDownload;
            }
        }

        /// <summary>
        /// Full path of the file to save downloaded bits.
        /// </summary>
        public string SaveLocation
        {
            get
            {
                return saveLocation;
            }
        }

        public override bool IsCancellable
        {
            get
            {
                return true;
            }
        }

        #endregion
        
        /// <summary>
        /// Creates a new download task for the provided URL. If file exists before, it will be overridden
        /// with the new file.
        /// </summary>
        /// <param name="url">URL of the file to download</param>
        /// <param name="saveLocation">Full path to save the file</param>
        public DownloadTask(Uri url, string saveLocation)
            : base(url + saveLocation)
        {
            this.urlToDownload = url;
            this.saveLocation = saveLocation;
        }

        /// <see cref="Task.PerformTask"/>
        public override void PerformTask()
        {
            BinaryReader urlDownloadFile = null;
            FileStream outStream = null;
            BinaryWriter downloadedFile = null;
            WebResponse response = null;

            try
            {
                // Get the new file.
                WebRequest request = WebRequest.Create(this.urlToDownload);
                request.PreAuthenticate = true;
                request.Proxy = WebRequest.GetSystemWebProxy();
                request.Proxy.Credentials = System.Net.CredentialCache.DefaultNetworkCredentials;
                request.Credentials = System.Net.CredentialCache.DefaultCredentials;
                response = (WebResponse)request.GetResponse();

                long totalLength = response.ContentLength;
                long lengthSoFar = 0;

                urlDownloadFile = new BinaryReader(response.GetResponseStream());

                outStream = new FileStream(this.saveLocation, FileMode.Create);
                downloadedFile = new BinaryWriter(outStream);

                byte[] buf = new byte[BLOCK];
                int charsRead = 0;
                while ((charsRead = urlDownloadFile.Read(buf, 0, BLOCK)) > 0)
                {
                    lock (this)
                    {
                        if (cancelFlag)
                        {
                            downloadedFile.Close();
                            downloadedFile = null;
                            try
                            {
                                File.Delete(this.saveLocation);
                            }
                            catch (DirectoryNotFoundException)
                            {
                            }
                            throw new TaskException(ExceptionStrings.CancelledString, this);
                        }
                    }
                    lengthSoFar += charsRead;

                    // totalLength will be -1 if the content length property is not set properly.
                    // If that's the case we want to treat progress in a binary way, going from 0 to 1.
                    if (totalLength <= 0)
                    {
                        this.ReportProgress(0, 1);
                    }
                    else
                    {
                        this.ReportProgress((int)lengthSoFar, (int)totalLength);
                    }
                    downloadedFile.Write(buf, 0, charsRead);
                }

                // If we had to fall back to not reporting download progress we want to report that the download is complete
                // by setting progress to 1.
                if (totalLength <= 0)
                {
                    this.ReportProgress(1, 1);
                }
            }
            catch (IOException ex)
            {
				throw new TaskException(ExceptionStrings.IOExceptionString, ex, this);
            }
            catch (ArgumentException ex)
            {
				throw new TaskException(ExceptionStrings.ArgumentExceptionString, ex, this);
            }
            catch (System.Security.SecurityException ex)
            {
                throw new TaskException(ex.Message, ex, this);
            }
            catch (WebException ex)
            {
                throw new TaskException(ex.Message, ex, this);
            }
            catch (NotSupportedException ex)
            {
                throw new TaskException(ex.Message, ex, this);
            }
            catch (UriFormatException ex)
            {
				throw new TaskException(ExceptionStrings.UrlFormattingExceptionString, ex, this);
            }
            finally
            {
                if (downloadedFile != null)
                    downloadedFile.Close();
                
                if (urlDownloadFile != null)
                    urlDownloadFile.Close();
                
                if (outStream != null)
                    outStream.Close();
                
                if (response != null)
                    response.Close();
            }
       }

        /// <summary>
        /// Stops downloading and deletes the downloaded file it possible
        /// </summary>
        protected override void CancelTaskInternal()
        {
            lock (this)
            {
                this.cancelFlag = true;
            }
        }
    }
}

